from Accounting.CashFlow.SubRed import *
from Accounting.Fee.FixedFee import *
from Accounting.Fee.FloatingFee import *
from Accounting.Fee.MiscFee import *
from Accounting.Fee.FailoverFee import *
from Accounting.Fee.ClientFeeAdj import *

from DataAccess.DBConnFactory import DBConnFactory
from string import Template


class EntityBase:
	def __init__(self, id, parent = None):
		self.id = id
		self.parent = parent
		
		#NOT init fee object here, 
		#create when required...

	def get_parent(self):
		return self.parent
		
	def get_entity_id(self):
		return self.id
		
	def settlement(self, ref_date, invested_asset_val, curncy):
		raise NotImplementedError
		
				
	#private	
	def create_fee(self, ref_date):
		#according to fee schedule build fee objects.
		cursor = DBConnFactory().get_db_connection('ACCOUNTING').cursor()
		sql_tpl = Template('''select conf.class,
									conf.subtype,
									sch.is_pay_day,
									conf.is_redem,
									curncy
							from fee_conf conf
							join fee_schedule sch
								on sch.id=conf.schedule
								and sch.ref_date=TO_DATE('${DATE}','yyyy-mm-dd')
								and conf.entity='${ENTITY}' 
							order by conf.sequence asc''')
		cursor.execute(sql_tpl.substitute(DATE=ref_date.isoformat(), ENTITY=self.id))	
		self.fee_list=[]
		for item in cursor.fetchall():
			class_name = item[0]
			subtype = item[1]
			is_payment_date = item[2]
			is_redem = item[3]
			curncy = item[4]
			impl_fee = globals()[class_name](ref_date, self, subtype, is_payment_date, is_redem, curncy)
			
			#wrapper the Faileover fee
			#to automaticly get official fee first.
			self.fee_list.append(FailoverFee(impl_fee))  
		
		
	#pirvate
	def impl_calc_category_fee_sum(self, ctgy):
		fee_sum = 0.0
		
		#print 'debug,', self.get_entity_id(), 'fee_list size=', len(self.fee_list)
		for fee_item in self.fee_list:
			if fee_item.category()==ctgy:
				fee_item.calculate()
				fee_item.persistence()
				fee_sum += fee_item.amount()  #get fee calculation result

		return fee_sum
		
	
	def cash_chg_by_fee_settlement(self, ref_date, fee_obj):
		if not fee_obj.is_pay_date():
			return 
			
		conn = DBConnFactory().get_db_connection('ACCOUNTING')
		
		curncy = fee_obj.currency()
		amount = fee_obj.amount()
		conn.cursor().execute(CASH_CHG_INSERT_SQL, (ref_date.isoformat(),
													self.get_entity_id(),
													-amount,
													curncy,
													"FEE",
													fee_obj.type()))
		conn.commit()
		
	
	def fee_settlement(self, ref_date):
		for fee_item in self.fee_list:
			self.cash_chg_by_fee_settlement(ref_date, fee_item)
					
			for act in fee_item.trigger_fund_share_chg(ref_date):
				self.parent.accept_fund_share_chg(act)	
	

	
	def residual_cash(self, ref_date):
		return 0
	
	

class Fund(EntityBase):
	#self.children_roster 

	def __init__(self, id, parent = None):
		EntityBase.__init__(self, id, parent)
		self.create_children()   #recursive creating...
			
	def create_children(self):
		raise NotImplementedError

	def query_children_id(self):
		cursor = DBConnFactory().get_db_connection('ACCOUNTING').cursor()
		cursor.execute("select distinct max(ref_date) latest_date from share_chg")
		r = cursor.fetchall()
		if (not r) or (len(r[0])<1) or (not r[0][0]):
			return []
		
		d = r[0][0].date()		
		sql_tpl = Template('''select distinct child 
							from share_account
							where parent='${PARENT}'
								and ref_date=TO_DATE('${DATE}','yyyy-mm-dd')
								and shares>0''')
		cursor.execute(sql_tpl.substitute(PARENT=self.id,
										DATE=d.isoformat()))
		#r = cursor.fetchall()
		return [item[0] for item in cursor.fetchall()]
	
	
	def find_child(self, id):		
		if id in self.children_roster:
			return self.children_roster[id]
		
		return None

	
	def add_child(self, id, entity):
		self.children_roster[id] = entity
		
		
	def accept_fund_share_chg(self, action):
		if isinstance(action, Subscription):
			self.purchase_fund(action)
		else:
			self.redeming_fund(action)
			
	
	def purchase_fund(self, purchase_action):
		purchase_action.persistence()
		
		#r = util_query_avpu(purchase_action.reference_date(), self.id)
		#if not r:
		#	raise AVPU_NA(), ("AVPU not available at " + purchase_action.reference_date().isoformat())			
		#share_add = purchase_action.amount()/r[0]
		
		if self.parent:  #has parent node
			purchase_action2 = Subscription(purchase_action.reference_date(), 
											purchase_action.amount(), 
											purchase_action.currency(), 
											'NAV',
											#share_amt_conv_type(purchase_action.reference_date(), self), 
											self)
			self.parent.purchase_fund(purchase_action2)
			
		
	def redeming_fund(self, redemption_action):
		redemption_action.persistence()
				
		if self.parent:  #has parent node		
			redemption_action2 = Redemption(redemption_action.reference_date(), 
											redemption_action.amount(), 
											redemption_action.currency(), 
											RedemptionByAmount, 
											None,          #corresponding_subscription_id
											'NAV', 
											self)          #entity
			self.parent.redeming_fund(redemption_action2)
				
	
	def settlement(self, ref_date, invested_asset_val, curncy):		
		#print 'debug, settlement at', self.id 
		
		self.create_fee(ref_date)
		asset_val = invested_asset_val+self.residual_cash(ref_date)
		children_av = self.calc_children_av(ref_date, asset_val, curncy)
		self.fee_settlement(ref_date)
		
		for child_item in children_av:
			child_obj = self.children_roster[child_item[0]]
			child_asset_val = child_item[1]
			child_obj.settlement(ref_date, child_asset_val, curncy)
		
		
		#private	
	def calc_children_av(self, ref_date, asset_val, curncy):
		conn = DBConnFactory().get_db_connection('ACCOUNTING')
		cursor = conn.cursor()
		sql_tpl = Template('''select child,shares 
							from share_account
							where parent='${ENTITY}' 
							and ref_date=TO_DATE('${DATE}','yyyy-mm-dd')
							and shares>0''')
		cursor.execute(sql_tpl.substitute(ENTITY=self.id, 
										DATE=ref_date.isoformat()))	
		r = cursor.fetchall()
		if not r:
			return []
		
		share_sum = reduce(lambda x,y: x+y[1], r, 0)
		assert share_sum > 0
	
		sql_text = '''insert into asset_value 
					(ref_date,entity,av,avpu,curncy,type)
					values(TO_DATE(:1,'yyyy-mm-dd'),:2,:3,:4,:5,:6)'''
		
		entry = (ref_date.isoformat(), self.id, asset_val, asset_val/share_sum, curncy, 'AV')
		cursor.execute(sql_text, entry)
		conn.commit()
		
		pre_iav_fee_sum = self.impl_calc_category_fee_sum('PreIAVFee')
		iav = asset_val-pre_iav_fee_sum
		iav_per_share = iav/share_sum
		entry = (ref_date.isoformat(), self.id, iav, iav_per_share, curncy, 'IAV')
		cursor.execute(sql_text, entry)
		conn.commit()
				
		pre_gav_fee_sum = self.impl_calc_category_fee_sum('PreGAVFee')		
		gav = iav-pre_gav_fee_sum
		gav_per_share = gav/share_sum
		entry = (ref_date.isoformat(), self.id, gav, gav_per_share, curncy, 'GAV')
		cursor.execute(sql_text, entry)
		conn.commit()
				
		pre_nva_fee_sum = self.impl_calc_category_fee_sum('PreNAVFee')
		nav = gav-pre_nva_fee_sum
		nav_per_share = nav/share_sum
		entry = (ref_date.isoformat(), self.id, nav, nav_per_share, curncy, 'NAV')
		cursor.execute(sql_text, entry)
		conn.commit()
				
		children_av = [(item[0],item[1]*nav_per_share) for item in r]
		return children_av
				
